package com.xm.spider;

import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.pdfwriter.compress.CompressParameters;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType0Font;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.*;

/**
 * pdf工具类
 */
@Slf4j
public class PDFUtil {
    private static final int PAGE_WIDTH = 595; // A4 Width in Points
    private static final int PAGE_HEIGHT = 842; // A4 Heigth in Points
    private static final int LEFT_MARGIN = 50;// 距左边距
    private static final int RIGHT_MARGIN = 50;// 距右边距
    private static final int BOTTOM_MARGIN = 50;// 距下边距
    private static final int TOP_MARGIN = 50;// 距上边距
    private static final float FONT_SIZE = 18;// 字体大小
    private static final float LEADING = 1.2f * FONT_SIZE; // 行间距
    public static final int HANG_MARGIN = 16;// 首行缩进像素

    public static List<File> enFonts;//英文字体集
    static {
        enFonts = new ArrayList<>();
        enFonts.add(0, new File("D:\\java\\download\\fonts\\Rainbow-Party-2.ttf"));//en
    }
    public static List<File> zhFonts;//中文字体集
    static {
        zhFonts = new ArrayList<>();
        zhFonts.add(0, new File("D:\\java\\download\\fonts\\AaErMoXingShu-2.ttf"));//zh
        zhFonts.add(1, new File("D:\\java\\download\\fonts\\AaDongQiChangYueYangLouJi-2.ttf"));//zh
        zhFonts.add(2, new File("D:\\java\\download\\fonts\\GuoFengLianPoFanTi-2.ttf"));//zh
        zhFonts.add(3, new File("D:\\java\\download\\fonts\\GuoFengMuLanXingShuJianTi-2.ttf"));//zh
        zhFonts.add(4, new File("D:\\java\\download\\fonts\\ZhongYingLiHeTieShanYingBiXingKai-1.ttf"));//zh
//        zhFonts.add(5, new File("D:\\java\\download\\fonts\\XiaoDouDaoMoMoZhiCaoShuJian-2.otf"));//zh
    }
    public static File font = zhFonts.get(0);// 自定义字体
    public static File font00 = zhFonts.get(0);// 自定义默认字体

    /**
     * 创建一个预览字体的pdf文件
     * @param folder pdf文件存放目录
     * @param filePath pdf文件名称
     * @param text pdf写入文本
     */
    public static void testZhPdf(String folder, String filePath, String text){
        try {
            File tmpFolder = new File(folder);
            if(!tmpFolder.exists()){//创建临时文件夹
                Files.createDirectory(tmpFolder.toPath());
            }

            List<String> imperfectFonts = new ArrayList<>();
            for (File everyFont : zhFonts) {
                Set<Character> unsupportCharSet = unsupportCharSet(everyFont, text);
                if(!unsupportCharSet.isEmpty()){
                    imperfectFonts.add("字体【" + everyFont.getName() + "】无法展示，因为不支持以下字符集：" + unsupportCharSet.toString());
                }
            }

            if(!imperfectFonts.isEmpty()){
                for (String imperfectFont : imperfectFonts) {
                    log.error(imperfectFont);
                }
                throw new RuntimeException("创建一个预览字体的pdf文件失败，详情见日志！");
            }

            try (PDDocument doc = new PDDocument()) {
                PDPage page = newA4Page();
                doc.addPage(page);
                float pageWidth = page.getMediaBox().getWidth();
                float pageHeight = page.getMediaBox().getHeight();
                float ty = pageHeight - 50;//起始高度
                float hangHeight = 1.2f * fontSize();//行间距
                PDPageContentStream contentStream = new PDPageContentStream(doc, page);
                for (File everyFont : zhFonts) {
                    //设置自定义字体
//                    PDType0Font pdType0Font = PDType0Font.load(doc, everyFont);
                    PDType0Font pdType0Font = PDType0Font.load(doc, Files.newInputStream(everyFont.toPath()), false);
                    contentStream.setFont(pdType0Font, fontSize());
                    float stringWidth = textLen(pdType0Font, text);
                    float tx = (pageWidth - stringWidth) / 2;//起始左边距
                    //测试文本写入
                    contentStream.beginText();
                    contentStream.newLineAtOffset(tx, ty);
                    contentStream.showText(text);
                    contentStream.endText();
                    //换行
                    ty -= hangHeight;
                }
                contentStream.close();

                doc.save(new File(filePath), CompressParameters.NO_COMPRESSION);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 根据文本生成pdf文件
     * @param font 字体文件
     * @param folder pdf文件存放目录
     * @param filePath pdf文件名称
     * @param text pdf写入文本
     */
    public static void newPdf(File font, String folder, String filePath, String text){
        try {
            File tmpFolder = new File(folder);
            if(!tmpFolder.exists()){//创建临时文件夹
                Files.createDirectory(tmpFolder.toPath());
            }

            //测试中文文本写入pdf文件
//          text = "第一章（nr）这地方臭气熏天。一种奇异而混杂的恶臭，只有冰才能掩盖南极营地的小木屋里充满了难闻的气味汗水，还有融化的海豹脂肪散发出的鱼油臭味。一个搽剂的泛音消除了汗渍的霉味snow-drenched毛皮。烧焦脂肪的刺鼻气味，还有动物，不难闻的狗的气味，被时间稀释，挂在空气中。(nr)残留的机油气味与…的污染形成鲜明对比马具敷料和皮革。然而，不知何故，通过所有的恶臭人类和他们的伙伴——狗、机器和烹饪——又来了一个污点。那是一件古怪的、弄皱脖子的东西，非常微弱在工业和生活的气味中有一种外来的气味。这是一种生活气息。但它来自被束缚的东西用绳子和防水油布铺在桌子上，慢慢地、有条不紊地滴着水躺在沉重的木板上，在没有遮挡的强光下，又湿又憔悴电灯。(nr)布莱尔，那个秃顶的小生物学家，抽搐了一下紧张地看着包裹物，下面露出清澈、黑暗的冰然后不安地把篷布拉回原位。他的小鸟——他的影子像被压抑的渴望的动作一样，在天空上舞动他赤裸的头骨周围僵硬的灰白头发形成了滑稽的光环关于影子的头。(nr)加里指挥官拂开宽松的西装腿内衣，然后向桌子走去。他的眼睛慢慢地注视着围成一圈的人挤进行政大楼。他高大僵硬的身体终于挺直了身子，点了点头。“三十,七个，都在这儿。”他的声音很低，却带着明确的权威是一个天生的指挥官，也是一个头衔。(nr)“你知道那个发现背后故事的梗概二次极地考察。我一直在和老二商量指挥官麦克雷蒂和诺里斯，还有布莱尔和布朗博士。铜。有不同的意见，因为它涉及到整个集团，它只是整个远征人员行动起来。(nr)“我要请麦克雷迪给你讲故事的细节，因为你们每个人都忙于自己的工作密切关注其他人的努力。McReady ?”(nr)从烟蓝色的背景中移开，麦克莱迪是一个人影从某个被遗忘的神话中，一个若隐若现的，承载生命的青铜雕像，和走。他身高六英尺四英寸，停在了桌子，并以特有的眼光向上看，以确保自己房间下横梁低矮，天花板挺直。他的粗糙的,他还穿着一件橘黄色的防风夹克，不过是他的巨大的框架，它似乎没有错位。即使在这里，四英尺以下在南极上空嗡嗡作响的漂流风天花板上，冰冻大陆的寒气渗了进来，散发出来意思是这个男人的严厉。他是青铜的——他的伟大红青铜色的胡子，浓密的头发与之相配。粗糙的,有绳的手握紧，放松，握紧，放松在桌上木板是青铜的。连深陷的眼睛都沉了下来眉毛被晒成了古铜色。(nr)耐老化耐久性的金属辐条在崎岖的重他的脸的轮廓，和那沉重的声音的圆润的音调。诺里斯和布莱尔在一件事上意见一致；我们发现的那只动物不是-陆地起源。诺里斯担心这可能会有危险；布莱尔说没有。(nr)“但我要回到我们是如何以及为什么找到它的问题上来。献给所有的一切在我们来这里之前，我们就知道这个点是在地球的南磁极上空。罗盘确实指向就在这里，你们都知道。更精密的仪器物理学家专门为此设计的仪器探险队及其对磁极的研究，发现了二次磁极二次效应，磁场影响较弱约80西南几英里处。(nr)“二次磁考察队去调查了。不需要讲细节。我们找到了，但不是很大或是诺里斯期望找到的磁山铁当然，矿石是磁性的；铁更是如此——还有某些特殊钢更具磁性。从表面的迹象来看，是次要的我们发现极很小，小到它的磁效应是荒谬的。任何磁性材料都不可能有这种效果。冰层上的探测显示，它就在其中一个区域内100英尺的冰川表面。(nr)“我认为你应该了解这个地方的结构。有一个广阔的高原，绵延150多英里范沃尔说，从第二站往南走。他没有时间和燃料都不足以飞得更远，但它正平稳地向南飞行然后。就在那里，那个被埋的东西，有一个冰被淹没的山脊，一堵坚不可摧的花岗岩墙这就把从南方来的冰击退了。(nr)“正南四百英里是南极高原。你问过我很多次为什么这里变暖了起风了，你们大多数人都知道。作为一名气象学家，我会的我发誓在零下70度的时候不会有风，不会超过5英里的风可以在零下50度下吹，而不会造成由于与地面、冰雪和空气的摩擦而变暖本身。(nr)“我们在那座被冰淹没的山脉的边缘扎营12天。我们在蓝冰里挖了个营地，形成了表面，并逃脱了大部分。而是连续12天风以每小时45英里的速度刮着。它曾高达48，然后降至有时。气温是零下63度。气温上升到零下60度降至零下68度。这在气象上是不可能的，而且还在继续12天12夜不间断。(nr)“在南方的某个地方，南极高原的冰冻空气从一万八千英尺高的碗状山口滑下，越过冰川，向北出发。一定有漏斗山脉引导着它，把它扫走了四百年距离我们找到第二极的秃高原还有几英里，再往北350英里到达南极大洋(nr)“自从南极洲冻结了2000万以来，那里就一直冻结着年前的事了。那里从来没有解冻过。(nr)“两千万年前，南极洲开始结冰。我们进行了调查、思考和推测。我们相信发生的就是这样。(nr)“有什么东西从太空下来了，一艘船。我们在蓝冰，就像没有指挥塔的潜艇指示叶片，长280英尺，直径45英尺厚的。(nr)“嗯，范·沃尔？”空间?是的，但我稍后会更好地解释。”麦克雷蒂用坚定的声音继续说下去。(nr)“它来自太空，由人类没有的力量驱动和举起还没被发现，不知怎么的，也许出了什么问题然后，它与地球磁场纠缠在一起。它从这里向南，出来了可能是控制的，绕着磁极转。那是野蛮人但当南极洲还在结冰的时候，它一定是我曾经比你野蛮一千倍。一定是下了暴风雪随着大陆的冰川作用，新的雪和漂移一样落下。那里的漩涡一定特别厉害，狂风呼啸那座已被掩埋的山的边缘覆盖着坚实的白色毯子。(nr)“船正面撞上了坚硬的花岗岩，破裂了。并不是每一个其中一名乘客遇难，但整艘船肯定已经遇难毁了，她的驱动装置被锁住了。它和地球的纠缠在一起诺里斯相信。任何由智慧生物创造的东西都不能与星球上无尽的自然力量纠缠在一起生存。(nr)一名乘客下了车。我们从未见过那里的风气温降至零下41度，从未超过零下60度。然后,风一定更大。有漂移落在实心板。那个‘东西’在十步之内就完全消失了。”他停顿了一会儿，那深沉、沉稳的声音让位于那刺耳的声音头顶上嗡嗡作响的风，还有那不安的、恶意的汩汩声厨房炉子的管子。(nr)一阵飘流的风从头顶刮过。现在雪花被咕咕的风吹起，一行行平地飘走，令人眼花缭乱穿过被掩埋的营地。如果一个男人走出连接每个营地建筑的隧道表面上，他十步内就会消失。在那里，修长的黑手指无线电桅杆有300英尺高，最高点是晴朗的夜空。稀薄的天空，呜呜作响的风不停地从越过另一个越过舔舐，卷曲的披风极光。在北方，地平线上闪烁着奇怪而愤怒的色彩午夜的黄昏。那是南极洲上空300英尺的春天。(nr)表面上是白色的死亡。死于针指感冒被风驱赶着，从任何温暖的事物中吸取热量。冷-而白雾无边无际，永无止尽的飘移，细，细的颗粒舔着遮住一切的雪。(nr)金纳，那个满脸伤疤的小个子厨子，畏缩了一下。五天前他有走到水面去拿冷冻牛肉。他有到了那里，就往回走——这时，从南方吹来了一阵风。冰冷、白色的死亡流过地面，使他睁不开眼睛二十秒。他在原地打转，踉踉跄跄。是半小时。一小时前，用绳子引导的人从下面发现了他密不透风的黑暗。(nr)人——或“物”——很容易在十步之内迷路。(nr)“当时的漂流风可能比我们更难以穿透知道。”麦克雷蒂的声音使金纳猛地回过神来。回欢迎光临，广告楼的潮湿温暖。“飞机上的乘客看来飞船也没准备好。它在十英尺之内就结冰了这艘船。(nr)“我们挖了下去寻找那艘船，我们的隧道碰巧找到了冷冻的动物。巴克莱的斧头——冰斧——击中了它的头骨。(nr)“当我们看到那是什么东西时，巴克莱回到拖拉机旁，把火点起来，当蒸汽压力增大时，就呼叫布莱尔和库珀博士。当时巴克莱本人也病了。呆在生病事实上，已经住了三天了。(nr)布莱尔和库珀来的时候，我们把那只动物切成了一块如你所见，冰把它包裹起来，装在拖拉机上准备运回在这里。我们想进入那艘飞船。(nr)“我们走到旁边，发现金属是我们不知道的东西知道。我们的铍青铜非磁性工具碰不到它。巴克莱在拖拉机上放了一些工具钢，那不会也不要抓。我们做了合理的测试——甚至试了一些酸没有结果的电池。(nr)“他们一定有一个钝化过程来制造镁金属是这样抗酸的，合金的耐酸度至少是95度百分之百的镁。但我们没办法猜出来，所以我们发现了那扇几乎没打开的门锁，就从里面钻了出来。有冰闸里是清澈坚硬的冰，我们够不到。透过小裂缝，我们可以看到里面只有金属里面有工具，所以我们决定用炸弹。(nr)“我们有癸烷炸弹和铝热剂。铝热剂是冰柔软剂;Decanite可能打碎了贵重的东西铝热剂的热量会使冰松动。库珀博士，诺里斯和我放了一枚25磅重的铝热剂炸弹，装上电线，拿走了接头沿着隧道爬到地面，布莱尔的蒸汽拖拉机就在那里等待。在我们设置的花岗岩墙的另一边一百码处铝热剂炸弹。(nr)“当然，船上的金属镁被卡住了。的光辉炸弹爆炸后熄灭，然后又开始爆炸。我们跑回去向拖拉机走去，渐渐地，那耀眼的光芒越来越强。从我们我们能从下面看到整个冰原吗带着难以忍受的光芒；船的影子是一个又大又黑的圆锥体向北走去，暮色即将降临一去不复返了。过了一会儿，我们又数了三个影子可能是其他乘客冻结在那里的东西。然后冰块哗啦哗啦地落下来撞在船上。(nr)“这就是我告诉你那个地方的原因。刮来的风从南极来的人就在我们身后。蒸汽和氢火焰被白色的冰雾撕裂；冰下燃烧的热量还没碰到我们，就被猛拽到南极海去了。否则我们就不会回来了，即使有……的庇护那座挡住光线的花岗岩山脊。(nr)“不知怎么的，在这令人眩目的地狱里，我们可以看到伟大的驼背东西，黑色的大块发光的东西，即便如此。他们连狂怒的人也不放过镁的白炽一时。那一定是引擎，我们知道。秘密闪耀着光辉——秘密可能给了人类行星神秘的事情能举起并投掷那艘船吗地球磁场。我看到诺里斯的嘴动了一下，便躲了起来。我我听不见他说什么。(nr)绝缘——某种东西——让位了。他们浸透了整个地球两千万年前才挣脱出来。天空中的极光被舔了下去，整个高原都沐浴在冰冷的火中那遮蔽了视野。我手里的冰镐烧得通红，然后在冰上发出嘶嘶声。我衣服上的金属纽扣烫着了我。一道电蓝色的闪光从花岗岩的另一边升起墙。(nr)接着，冰墙轰然倒塌在它身上。有那么一瞬间就像干冰被金属挤压时发出的尖叫声一样。(nr)“我们瞎了眼，在黑暗中摸索了好几个小时恢复。我们发现一英里内的所有线圈都是熔断的垃圾，发电机，每台收音机，耳机和扬声器。如果我们如果没有蒸汽拖拉机，我们就无法到达二级营地。(nr)“凡·沃尔在太阳升起的时候从大磁石飞来，你知道的。我们我尽快回家了。这就是——那的历史。”麦克雷蒂的青铜大胡子指了指桌子上的东西表格。(nr)(nr)";
            //中文文本换行处理（因为某些字体不支持特殊字符，所以转中文必须执行，否则会报错）
            text = text.replaceAll("\\(nr\\)\\(nr\\)", "(nr)")
                    .replaceAll("（nr）", "\n \r")
                    .replaceAll("\\(nr\\)", "\n \r");
            text = PDFUtil.toLegalText(font, text, " ");
            try (PDDocument doc = new PDDocument()) {
                //自动写入文本并处理换行
                autoWriteText(doc, text, font);
//                autoWriteTextNew(doc, text, font);
                doc.save(new File(filePath), CompressParameters.NO_COMPRESSION);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 根据文本数量批量生成pdf文件
     * @param font 字体文件
     * @param folder pdf文件存放目录
     * @param textList pdf写入文本（每个文本生成一个pdf）
     */
    public static void batchPdf(File font, String folder, List<String> textList){
        try {
            if(!textList.isEmpty()){
                File tmpFolder = new File(folder);
                if(!tmpFolder.exists()){//创建临时文件夹
                    Files.createDirectory(tmpFolder.toPath());
                }

                for (int i = 0; i < textList.size(); i++) {
                    String text = textList.get(i);
                    //TEST "D:\\data\\新建文件夹\\splitChapter\\Chapter_1.pdf"
                    newPdf(font, folder, folder + "\\Chapter_" + (i + 1) + ".pdf", text);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    //自动写入文本并处理换行（重构）
    public static void autoWriteTextNew(PDDocument doc, String text, File font) throws IOException {
        doc.addPage(newA4Page());
        PDPageContentStream contentStream = new PDPageContentStream(doc, doc.getPage(0));
        //获取默认字体
        PDType0Font pdType0Font00 = PDType0Font.load(doc, font00);
        //获取当前字体
        PDType0Font myPdType0Font = PDType0Font.load(doc, font);
        //文本不支持字符集
        Set<Character> unsupportCharSet = unsupportCharSet(font, text);

        //处理文本，获取段落
        String[] textArr = text.split("\n \r");
        //定义写入位置、写入高度、结束高度(固定)
        float startX00 = leftMargin() + hangMargin();
        float startX = startX00;
        float startY = topMargin();
        for (int j = 0; j < textArr.length; j++) {
            String paragraph = textArr[j];
            //pdfbox无法处理\n \r
            paragraph = paragraph.replace("\n", "").replace("\r", "");
            //拆分段落，逐字写入（英文时拆会分成单个词组，中文时会拆分成单个字）
            String[] words = paragraph.split("(?<=\\W)|(?=\\W)");

            //循环文本字符集，重新拼接字符集满一行再写入
            //定义当前行累计文本长度、字体字符长度
            float lineWidth = 0;
            float additionalWordLength = 0;
            StringBuilder lineBuffer = new StringBuilder();
            String word;
            int length = words.length;
            for (int i = 0; i < length; i++) {

            }

            startY -= LEADING*2;//设置写入高度，段落中间空一行
        }
        contentStream.close();
    }

    /**
     * 自动写入文本并处理换行
     * @param doc 文档流
     * @param text 待写的文本
     * @param font 字体文件
     * @throws IOException 如果出现 I/O 错误
     */
    public static void autoWriteText(PDDocument doc, String text, File font) throws IOException {
        //首页或自动换行时超过纸张大小 自动加一页；
        doc.addPage(newA4Page());
        PDPageContentStream contentStream = new PDPageContentStream(doc, doc.getPage(0));
        //设置自定义字体
        PDType0Font myPdType0Font = PDType0Font.load(doc, font);
        contentStream.setFont(myPdType0Font, fontSize());

        float startX = leftMargin();//当前行的起始 X 坐标
        float endX = rightMargin();//当前行的终止 X 坐标
        float startY = topMargin();//当前行的起始 Y 坐标

        //文本不支持字符集
        Set<Character> unsupportCharSet = unsupportCharSet(font, text);
        if(!unsupportCharSet.isEmpty()){
            throw new RuntimeException("当前字体缺少部分汉字，请更换字体。");
        }

        //处理段落
        String[] textArr = text.split("\n \r");
        boolean isFirst = true;
        for (String paragraph : textArr) {
            log.info("paragraph：{}", paragraph);
            if(isFirst){//每页第一行不换行
                isFirst = false;
            }
            startX = leftMargin() + hangMargin();

            //pdfbox无法处理\n \r
            paragraph = paragraph.replace("\n", "").replace("\r", "");
            //处理段落单词
            String[] words = paragraph.split("(?<=\\W)|(?=\\W)");//英文字词拆分
            StringBuilder lineBuffer = new StringBuilder();
            float lineWidth = 0;
            float additionalWordLength = 0;

            for (String word : words) {
//                log.info("word：{}", word);//中文将会拆分成字，英文则会拆分成字词
                contentStream.setFont(myPdType0Font, fontSize());
                additionalWordLength = textLen(myPdType0Font, word);

                boolean canContinue = (lineWidth + additionalWordLength) <= (endX - startX);//预留长度允许写入当前字符
                if (canContinue) {//继续写
                    lineBuffer.append(word);
                    lineWidth += additionalWordLength;
                } else {//换行，先写入再换行（写的当前行 一定会写在当前页）
                    log.info("word={}，startX={}", word, startX);
                    writeAndMoveToNextLine(contentStream, startX, startY, lineBuffer.toString());
                    startX = leftMargin();
                    startY -= LEADING;
                    lineBuffer.setLength(0); // Clear the buffer
                    lineBuffer.append(word);
                    lineWidth = additionalWordLength;

                    if(startY <= bottomMargin()){
                        contentStream.close();
                        //当自动换行时超过纸张大小时，新建一页并重新开始读写
                        doc.addPage(newA4Page());
                        contentStream = new PDPageContentStream(doc, doc.getPage(doc.getNumberOfPages() - 1));
                        contentStream.setFont(myPdType0Font, fontSize());
                        startX = leftMargin();
                        endX = rightMargin();
                        startY = topMargin();
                        isFirst = true;
                    }
                }
            }

            // Write any remaining text on the last line
            if (lineBuffer.length() > 0) {// 存在当前未满一行数据，则单独换行写入
                writeAndMoveToNextLine(contentStream, startX, startY, lineBuffer.toString());
            }
            startY -= LEADING*2;
        }
        contentStream.close();
    }

    /**
     * 新建一个 pdf A4纸张
     */
    public static PDPage newA4Page(){
        PDPage page = new PDPage();
        //设置A4纸张大小
        page.setMediaBox(PDRectangle.A4);
        return page;
    }

    /**
     * 计算左侧边缘坐标
     * @return 左侧边缘坐标
     */
    public static float leftMargin() {
        return LEFT_MARGIN;
    }

    /**
     * 计算右侧边缘坐标
     * @return 右侧边缘坐标
     */
    public static float rightMargin() {
        return PAGE_WIDTH - RIGHT_MARGIN;
    }

    /**
     * 计算顶部边缘坐标
     * @return 顶部边缘坐标
     */
    public static float topMargin() {
        return PAGE_HEIGHT - TOP_MARGIN;
    }

    /**
     * 计算尾部边缘坐标
     * @return 尾部边缘坐标
     */
    public static float bottomMargin() {
        return BOTTOM_MARGIN;
    }

    /**
     * 首行缩进像素
     * @return 首行缩进像素
     */
    public static float hangMargin() {
        return HANG_MARGIN;
    }

    /**
     * 字体大小
     * @return 字体大小
     */
    public static float fontSize() {
        return FONT_SIZE;
    }

    /**
     * 获取字体文本长度
     * @param font 字体
     * @param text 文本
     * @return 像素
     */
    private static float textLen(PDFont font, String text) throws IOException {
        return font.getStringWidth(text)*FONT_SIZE/1000;
    }

    /**
     * 写入一行文本并在完成后移动到下一行
     *
     * @param contentStream 内容流
     * @param startX      当前行的起始 X 坐标
     * @param startY      当前 Y 坐标
     * @param text        待写的文本
     * @throws IOException 如果出现 I/O 错误
     */
    private static void writeAndMoveToNextLine(
            PDPageContentStream contentStream,
            float startX,
            float startY,
            String text) throws IOException {
        contentStream.beginText();
        contentStream.newLineAtOffset(startX, startY);
        contentStream.showText(text.trim()); // Remove trailing spaces
        contentStream.endText();
    }

    private static void writeWords(
            PDPageContentStream contentStream,
            float startX,
            float startY,
            String text) throws IOException {
        log.info("writeWords：{}，{}，{}", startX, startY, text);
        contentStream.beginText();
        contentStream.newLineAtOffset(startX, startY);
        contentStream.showText(text.trim()); // Remove trailing spaces
        contentStream.endText();
    }

    /**
     * 将字体 文本标签符号合法化
     * @param font 字体文件
     * @param text 文本
     * @param replaceC 替换符号
     * @return 返回合法化字体文本
     */
    public static String toLegalText(File font, String text, String replaceC){
        log.warn("警告：****************本次执行将替换不支持的字符为【{}】", replaceC);
        Set<Character> unsupportPunctuationSet = unsupportPunctuationSet(font);
        List<String> transList = new ArrayList<>();
        //替换不存在的字符
        StringBuilder textBuilder = new StringBuilder(text);
        int count = 0;
        String trans = "";
        for (int i = 0; i < textBuilder.length(); i++) {
            char c = textBuilder.charAt(i);
            if(unsupportPunctuationSet.contains(c)){
                textBuilder.replace(i, i + 1, replaceC);
                ++ count;

                trans = "已将字符【" + c + "】替换为【" + replaceC + "】";
                if(!transList.contains(trans)){
                    transList.add(trans);
                }
            }
        }

        log.warn("警告：本次共计转换{}处地方", count);
        for (String transContent : transList) {
            log.warn("警告：{}", transContent);
        }
        return textBuilder.toString();
    }

    /**
     * 获取字体不支持标点符号合集
     * @param font 字体文件
     * @return 返回字体不支持标点符号合集
     */
    public static Set<Character> unsupportPunctuationSet(File font){
        Set<Character> unsupportPunctuationSet = new HashSet<>();
        try (PDDocument doc = new PDDocument()) {
            PDType0Font pdType0Font = PDType0Font.load(doc, font);
            //标点符号检测
            Set<Character> allPunctuationSet = Punctuation.getAllPunctuationSet();
            String punctuations = allPunctuationSet.toString();

            StringBuilder punctuationBuilder = new StringBuilder(punctuations);
            //检查每个字符是否存在于字体中
            for (int i = 0; i < punctuationBuilder.length(); i++) {
                char c = punctuationBuilder.charAt(i);
                int codePoint = Character.codePointAt(punctuationBuilder, i);
                boolean hasChar = false;
                byte gid = -1;
                try {
                    if(Character.isWhitespace(codePoint)){
                        hasChar = true;
                    }

                    gid = pdType0Font.encode(Character.toString(c))[0];
                    if (gid >= 0) {
                        hasChar = true;
                    }
                } catch (Exception e) {
//                    log.error("当前字体字符判断：{}， {}", gid, c);
                }

                if (!hasChar) {
//                    log.info("当前字体字符判断：{}， {}", gid, c);
                    unsupportPunctuationSet.add(c);
                }
            }

            log.warn("警告：不支持的标签符号集：当前字体不支持的字符共计{}个：{}", unsupportPunctuationSet.size(), unsupportPunctuationSet);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return unsupportPunctuationSet;
    }

    /**
     * 按分区大小重新对数组分组
     * @param originalArray 原始字符串数组，如，{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"}
     * @param partitionSize 分区大小
     * @return 返回分组集合
     */
    public static List<List<String>> groupSize(String[] originalArray, int partitionSize){
        List<List<String>> chunks = new ArrayList<>();
        int len = originalArray.length;
        for (int i = 0; i < len; i += partitionSize) {
            int endIndex = Math.min(i + partitionSize, len);
            List<String> sublist = Arrays.asList(Arrays.copyOfRange(originalArray, i, endIndex));
            chunks.add(sublist);
        }
        return chunks;
    }

    /**
     * 获取当前文本字体不支持字符集
     * @param font 字体文件
     * @param text 当前文本
     * @return 返回当前文本字体不支持字符集
     */
    public static Set<Character> unsupportCharSet(File font, String text){
        Set<Character> unsupportCharSet = new HashSet<>();
        try (PDDocument doc = new PDDocument()) {
            PDType0Font pdType0Font = PDType0Font.load(doc, font);
            //文本字符检测
            StringBuilder punctuationBuilder = new StringBuilder(text);
            //检查每个字符是否存在于字体中
            for (int i = 0; i < punctuationBuilder.length(); i++) {
                char c = punctuationBuilder.charAt(i);
                int codePoint = Character.codePointAt(punctuationBuilder, i);
                boolean hasChar = false;
                byte gid = -1;
                try {
                    if(Character.isWhitespace(codePoint)){
                        hasChar = true;
                    }

                    gid = pdType0Font.encode(Character.toString(c))[0];
                    if (gid >= 0) {
                        hasChar = true;
                    }
                } catch (Exception e) {
//                    log.error("当前字体字符判断：{}， {}", gid, c);
                }

                if (!hasChar) {
//                    log.info("当前字体字符判断：{}， {}", gid, c);
                    unsupportCharSet.add(c);
                }
            }

            log.warn("警告：不支持的字符集：当前字体不支持的文本字符共计{}个：{}", unsupportCharSet.size(), unsupportCharSet);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return unsupportCharSet;
    }

}
